/*
 * libnat64 - LD_PRELOAD library to change ipv4 library calls to use nat64
 * Copyright (C) 2010 Daniel Drown
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <errno.h>

#define NAT64_CONF "/etc/nat64.conf"

#define IPV4_TO_IPV6_MAPPING_0 0x0
#define IPV4_TO_IPV6_MAPPING_1 0x0
#define IPV4_TO_IPV6_MAPPING_2 0xffff0000

#define IPV4_LOCALHOST 0x0100007f
#define IPV4_BINDANY 0x0

#define IPV6_LOCAL_0 0x0
#define IPV6_LOCAL_1 0x0
#define IPV6_LOCAL_2 0x0
#define IPV6_LOCALHOST_3 0x1000000
#define IPV6_BINDANY_3 0x0

static struct in6_addr nat64_addr;
static int has_nat64_addr = 0;

/*****************************************************
 * utility functions
 */

void check_nat64_conf() { // XXX: is this thread-safe?
  char nat64_str[INET6_ADDRSTRLEN];
  char *newline;
  int nat64_fd;
  ssize_t readlen;

  has_nat64_addr = 0;

  nat64_fd = open(NAT64_CONF, O_RDONLY);
  if(nat64_fd == -1) {
    return;
  }
  readlen = read(nat64_fd, nat64_str, INET6_ADDRSTRLEN-1);
  close(nat64_fd);
  if(readlen < 1) { // on errors or empty files
    return;
  }
  nat64_str[readlen] = '\0';
  if((newline = strchr(nat64_str,'\n'))) {
    newline[0] = '\0';
 }

  if(inet_pton(AF_INET6, nat64_str, &nat64_addr) == 1) {
    has_nat64_addr = 1;
  }
}

// only call this when:
// addr has space for a struct sockaddr_in6
// addr has a struct sockaddr_in
// has_nat64_addr is true
static void rewrite_outgoing_nat64_sockaddr(struct sockaddr_in6 *addr) {
  struct sockaddr_in *in_addr = (struct sockaddr_in *)addr;
  struct sockaddr_in6 in6_addr;

  in6_addr.sin6_family = AF_INET6;
  in6_addr.sin6_port = in_addr->sin_port;
  in6_addr.sin6_flowinfo = 0;
  in6_addr.sin6_scope_id = 0;
  if(in_addr->sin_addr.s_addr == IPV4_LOCALHOST) { // this is a problem for things like /etc/resolv.conf
    in6_addr.sin6_addr.s6_addr32[0] = IPV4_TO_IPV6_MAPPING_0;
    in6_addr.sin6_addr.s6_addr32[1] = IPV4_TO_IPV6_MAPPING_1;
    in6_addr.sin6_addr.s6_addr32[2] = IPV4_TO_IPV6_MAPPING_2;
    in6_addr.sin6_addr.s6_addr32[3] = in_addr->sin_addr.s_addr;
  } else {
    memcpy(&(in6_addr.sin6_addr),&nat64_addr,sizeof(nat64_addr));
    in6_addr.sin6_addr.s6_addr32[3] = in_addr->sin_addr.s_addr;
  }

  memcpy(addr, &in6_addr, sizeof(in6_addr));
}

static void rewrite_incoming_nat64_sockaddr(struct sockaddr *dest_addr, socklen_t *destlen, struct sockaddr_in6 *src_addr, socklen_t srclen) {
  if((src_addr->sin6_family == AF_INET6) && (*destlen >= sizeof(struct sockaddr_in))) {
    // it came from v6 and we have enough space for a v4 address
    struct sockaddr_in *in_addr = (struct sockaddr_in *)dest_addr;

    if(
        (src_addr->sin6_addr.s6_addr32[0] == IPV4_TO_IPV6_MAPPING_0) && 
        (src_addr->sin6_addr.s6_addr32[1] == IPV4_TO_IPV6_MAPPING_1) &&
        (src_addr->sin6_addr.s6_addr32[2] == IPV4_TO_IPV6_MAPPING_2)
      ) {
      // it came from a v4 mapping (most likely: localhost)
      in_addr->sin_addr.s_addr = src_addr->sin6_addr.s6_addr32[3];
      in_addr->sin_port = src_addr->sin6_port;
      in_addr->sin_family = AF_INET;
      *destlen = sizeof(struct sockaddr_in);
    } else if(memcmp(src_addr->sin6_addr.s6_addr32,nat64_addr.s6_addr32,sizeof(uint32_t)*3) == 0) {
      // it came from a nat64 mapping
      in_addr->sin_addr.s_addr = src_addr->sin6_addr.s6_addr32[3];
      in_addr->sin_port = src_addr->sin6_port;
      in_addr->sin_family = AF_INET;
      *destlen = sizeof(struct sockaddr_in);
    } else {
      // it came from v6
      if(*destlen > sizeof(struct sockaddr_in6)) {
        *destlen = sizeof(struct sockaddr_in6);
      }
      memcpy(dest_addr, src_addr, *destlen);
      *destlen = srclen;
    }
  } else {
    // it either didn't come from ipv6, or we don't have enough space for an ipv4 addr
    if(*destlen > sizeof(struct sockaddr_in6)) {
      *destlen = sizeof(struct sockaddr_in6);
    }
    memcpy(dest_addr, src_addr, *destlen);
    *destlen = srclen;
  }
}

void dump_sockaddr(const struct sockaddr *sa) {
  printf("family = %d\n", sa->sa_family);
  if(sa->sa_family == AF_INET) {
//    const struct sockaddr_in *in = (const struct sockaddr_in *)sa;
    printf("(AF_INET)\n");
  } else if(sa->sa_family == AF_INET6) {
//    const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)sa;
    printf("(AF_INET6)\n");
  }
}

/*****************************************************
 * system calls start here
 */

/* TODO
 * getpeername
 */

int last_socket_bound = -1;
uint32_t last_ipv4_bound;

int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
  typedef int (*getsockname_function_type)(int, struct sockaddr *, socklen_t *);

  getsockname_function_type next_getsockname = dlsym(RTLD_NEXT, "getsockname");

  if(has_nat64_addr && (sockfd == last_socket_bound)) {
    int retval;
    struct sockaddr_in6 real_sockaddr;
    struct sockaddr_in fake_sockaddr;
    socklen_t realsize;

    realsize = sizeof(real_sockaddr);

    retval = next_getsockname(sockfd, (struct sockaddr *)&real_sockaddr, &realsize);
    if(retval < 0) {
      return retval;
    }

    if(real_sockaddr.sin6_family != AF_INET6) { // that's wierd...
      last_socket_bound = -1;
      if(*addrlen > sizeof(real_sockaddr)) {
        *addrlen = sizeof(real_sockaddr);
      }
      memcpy(addr, &real_sockaddr, *addrlen);
      *addrlen = realsize;
      return retval;
    }

    fake_sockaddr.sin_port = real_sockaddr.sin6_port;
    fake_sockaddr.sin_family = AF_INET;
    fake_sockaddr.sin_addr.s_addr = last_ipv4_bound;
    
    if(*addrlen > sizeof(fake_sockaddr)) {
      *addrlen = sizeof(fake_sockaddr);
    }
    memcpy(addr, &fake_sockaddr, *addrlen);
    *addrlen = sizeof(fake_sockaddr);

    return retval;
  }

  return next_getsockname(sockfd, addr, addrlen);
}

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
  typedef int (*bind_function_type)(int, const struct sockaddr *, socklen_t);

  bind_function_type next_bind = dlsym(RTLD_NEXT, "bind");

  if(has_nat64_addr) {
    if(addr->sa_family == AF_INET) {
      struct sockaddr_in *in_addr = (struct sockaddr_in *)addr;
      struct sockaddr_in6 in6_addr;
      int was_localhost = 0;

      in6_addr.sin6_family = AF_INET6;
      in6_addr.sin6_port = in_addr->sin_port;
      in6_addr.sin6_flowinfo = 0;
      in6_addr.sin6_scope_id = 0;
      in6_addr.sin6_addr.s6_addr32[0] = IPV6_LOCAL_0;
      in6_addr.sin6_addr.s6_addr32[1] = IPV6_LOCAL_1;
      in6_addr.sin6_addr.s6_addr32[2] = IPV6_LOCAL_2;
      if(in_addr->sin_addr.s_addr == IPV4_LOCALHOST) {
        in6_addr.sin6_addr.s6_addr32[0] = IPV4_TO_IPV6_MAPPING_0;
        in6_addr.sin6_addr.s6_addr32[1] = IPV4_TO_IPV6_MAPPING_1;
        in6_addr.sin6_addr.s6_addr32[2] = IPV4_TO_IPV6_MAPPING_2;
        in6_addr.sin6_addr.s6_addr32[3] = IPV4_LOCALHOST;
        was_localhost = 1;
      } else if(in_addr->sin_addr.s_addr == IPV4_BINDANY) {
        in6_addr.sin6_addr.s6_addr32[3] = IPV6_BINDANY_3;
      } else {
        last_socket_bound = sockfd;
	last_ipv4_bound = in_addr->sin_addr.s_addr;
        // requested a specific ip to bind to, but that doesn't translate well
        in6_addr.sin6_addr.s6_addr32[3] = IPV6_BINDANY_3;
      }

      int retval;
      retval = next_bind(sockfd, (struct sockaddr *)&in6_addr, sizeof(in6_addr));

      if((retval < 0) && (errno == EADDRINUSE) && was_localhost) {
        errno = EADDRNOTAVAIL; // workaround for sophia-sip
      }

      return retval;
    }
  }

  return next_bind(sockfd, addr, addrlen);
}

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {
  typedef ssize_t (*sendto_function_type)(int, const void *, size_t, int, const struct sockaddr *, socklen_t);

  sendto_function_type next_sendto = dlsym(RTLD_NEXT, "sendto");

  if(dest_addr && dest_addr->sa_family == AF_INET && has_nat64_addr) {
    struct sockaddr_in6 newaddr;

    if(addrlen > sizeof(newaddr)) {
      addrlen = sizeof(newaddr);
    }
    memcpy(&newaddr,dest_addr,addrlen);
    rewrite_outgoing_nat64_sockaddr(&newaddr);
    return next_sendto(sockfd, buf, len, flags, (struct sockaddr *)&newaddr, sizeof(newaddr));
  }

  return next_sendto(sockfd, buf, len, flags, dest_addr, addrlen);
}

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {
  typedef ssize_t (*sendmsg_function_type)(int, const struct msghdr *, int);

  sendmsg_function_type next_sendmsg = dlsym(RTLD_NEXT, "sendmsg");

  if(msg && msg->msg_name && ((struct sockaddr *)msg->msg_name)->sa_family == AF_INET && has_nat64_addr) {
    struct msghdr msg_copy;
    struct sockaddr_in6 newaddr;

    memcpy(&msg_copy, msg, sizeof(msg_copy));
    if(msg_copy.msg_namelen > sizeof(newaddr)) {
      msg_copy.msg_namelen = sizeof(newaddr);
    }
    memcpy(&newaddr,msg_copy.msg_name,msg_copy.msg_namelen);
    rewrite_outgoing_nat64_sockaddr(&newaddr);
    msg_copy.msg_name = &newaddr;
    msg_copy.msg_namelen = sizeof(newaddr);
    return next_sendmsg(sockfd, &msg_copy, flags);
  }

  return next_sendmsg(sockfd, msg, flags);
}

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) {
  typedef ssize_t (*recvmsg_function_type)(int, struct msghdr *, int);

  recvmsg_function_type next_recvmsg = dlsym(RTLD_NEXT, "recvmsg");

  if(msg && msg->msg_name && has_nat64_addr) {
    // possibly rewrite src_addr
    ssize_t recvlen; 
    struct sockaddr_in6 addr;
    struct sockaddr *orig_addr = msg->msg_name;
    socklen_t orig_len = msg->msg_namelen;

    msg->msg_namelen = sizeof(addr);
    msg->msg_name = &addr;

    recvlen = next_recvmsg(sockfd, msg, flags);

    msg->msg_name = orig_addr;

    if(recvlen > 0) {
      rewrite_incoming_nat64_sockaddr(msg->msg_name,&orig_len,&addr,msg->msg_namelen);
      msg->msg_namelen = orig_len;
    }

    return recvlen;
  }

  return next_recvmsg(sockfd, msg, flags);
}

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) {
  typedef ssize_t (*recvfrom_function_type)(int, void*, size_t, int, struct sockaddr *, socklen_t *);

  recvfrom_function_type next_recvfrom = dlsym(RTLD_NEXT, "recvfrom");

  if(src_addr && has_nat64_addr) {
    // possibly rewrite src_addr
    ssize_t recvlen; 
    struct sockaddr_in6 real_addr;
    socklen_t real_addr_len;

    real_addr_len = sizeof(real_addr);

    recvlen = next_recvfrom(sockfd, buf, len, flags, (struct sockaddr *)&real_addr, &real_addr_len);

    if(recvlen > 0) {
      rewrite_incoming_nat64_sockaddr(src_addr,addrlen,&real_addr,real_addr_len);
    }
    
    return recvlen;
  } else {
    return next_recvfrom(sockfd, buf, len, flags, src_addr, addrlen);
  }
}

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
  typedef int (*connect_function_type)(int, const struct sockaddr *, socklen_t);

  connect_function_type next_connect = dlsym(RTLD_NEXT, "connect");

  if(addr && addr->sa_family == AF_INET && has_nat64_addr) {
    struct sockaddr_in6 newaddr;

    if(addrlen > sizeof(newaddr)) {
      addrlen = sizeof(newaddr);
    }
    memcpy(&newaddr,addr,addrlen);
    rewrite_outgoing_nat64_sockaddr(&newaddr);
    return next_connect(sockfd, (struct sockaddr *)&newaddr, sizeof(newaddr));
  } else {
    return next_connect(sockfd, addr, addrlen);
  }
}

int socket(int domain, int type, int protocol) {
  typedef int (*socket_function_type)(int,int,int);
  socket_function_type next_socket = dlsym(RTLD_NEXT, "socket");

  if((domain == AF_INET) && (type == SOCK_DGRAM || type == SOCK_STREAM)) { // only change TCP and UDP
    if((protocol == IPPROTO_IP) && (type == SOCK_DGRAM) && getenv("dhcptype")) {
      // avoid changing dhcp sub processes
    } else {
      check_nat64_conf();

      if(has_nat64_addr) {
        domain = AF_INET6;
      }
    }
  }

  /* call next socket */
  return next_socket(domain, type, protocol);
}

int getifaddrs(struct ifaddrs **ifap) {
  typedef int (*getifaddrs_function_type)(struct ifaddrs **);

  getifaddrs_function_type next_getifaddrs = dlsym(RTLD_NEXT, "getifaddrs");
  int retval;

  retval = next_getifaddrs(ifap);
  if(retval == 0) {
    check_nat64_conf();

    if(has_nat64_addr) {
      // pretend like we have an interface
      struct ifaddrs *interface;
      struct sockaddr_in *if_address, *if_netmask, *if_destaddr;

      interface = malloc(sizeof(struct ifaddrs));
      if(!interface) {
	return retval; // just give up and leave it as-is on memory allocation error
      }
      if_address = malloc(sizeof(struct sockaddr_in));
      if(!if_address) {
	free(interface);
	return retval;
      }
      if_netmask = malloc(sizeof(struct sockaddr_in));
      if(!if_netmask) {
	free(interface);
	free(if_address);
	return retval;
      }
      if_destaddr = malloc(sizeof(struct sockaddr_in));
      if(!if_destaddr) {
	free(interface);
	free(if_address);
	free(if_netmask);
	return retval;
      }

      interface->ifa_next = *ifap;
      *ifap = interface;

      interface->ifa_name = "gprs0";
      interface->ifa_flags = IFF_POINTOPOINT|IFF_UP|IFF_RUNNING|IFF_NOARP;

      interface->ifa_addr = (struct sockaddr *)if_address;
      if_address->sin_family = AF_INET;
      if_address->sin_addr.s_addr = 0xfeffff0a; // 10.255.255.254
      if_address->sin_port = 0;

      interface->ifa_netmask = (struct sockaddr *)if_netmask;
      if_netmask->sin_family = AF_INET;
      if_netmask->sin_addr.s_addr = 0xfcffffff; // 255.255.255.252
      if_netmask->sin_port = 0;

      interface->ifa_dstaddr = (struct sockaddr *)if_destaddr;
      if_destaddr->sin_family = AF_INET;
      if_destaddr->sin_addr.s_addr = 0xfdffff0a; // 10.255.255.253
      if_destaddr->sin_port = 0;

      interface->ifa_data = NULL;
    }
  }

  return retval;
}
